#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE-1)
#define PWM_ENABLE_REG							"0x2807e020"
#define PWM_ENABLE_VAL							"0xff"
#define GPIO_PAD_MODE_PWM0_CHANNEL0_REG			"0x32B30050"
#define GPIO_PAD_MODE_PWM2_CHANNEL0_REG			"0x32B30060"
#define GPIO_PAD_MODE_PWM0_CHANNEL0_VAL			"0x141" //(?)
#define GPIO_PAD_MODE_PWM2_CHANNEL0_VAL			"0x141"
#define PWM0_CONTROLLER							"0"
#define PWM0_CHANNEL0							"0"
#define PWM2_CONTROLLER							"2"
#define PWM2_CHANNEL0							"2"
#define WRITE_TYPE_W							"W"
#define WRITE_TYPE_H							"H"
#define WRITE_TYPE_B							"B"
#define PWM_PERIOD								"1000000"
#define PWM_DUTY1								"0"
#define PWM_DUTY2								"1000000"

enum PWMX {
	PWM0,
	PWM2,
	PWM0_PWM2,
	PWMMAX
};

static char pwm0_path[128];
static char pwm2_path[128];
/***********************************************************************
func: 将type指定的类型(数据宽度)的data数据保存到addr映射的虚拟地址空间中
param: addr 传入的物理地址
	   type 访问内存的类型 字(W) 半字(H) 字节(B)
	   data 要写入虚拟地址空间中的数据
************************************************************************/
int gpio_mode (char *addr, char *type, char* data)
{
	int fd;
	int type_access = 'w';
	off_t target;
	void *map_base, *virt_addr;
	unsigned long writeval;
	if (0 > (fd = open ("/dev/mem", O_ASYNC | O_RDWR)))
	{
		perror ("open /dev/mem error!");
		return -1;
	}
	
	target = strtoul (addr, 0, 0);
	type_access = tolower (type[0]);
	writeval = strtoul (data, 0, 0);
	map_base = mmap (0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, target & ~MAP_MASK);
	if (map_base == (void *)(-1)) {
		perror ("mmap");
		return -1;
	}
	//计算映射之后的虚拟地址
	virt_addr = map_base + (target & MAP_MASK);
	switch (type_access) {
		case 'b':
			*((unsigned char *) virt_addr) = writeval;
		break;
		case 'h':
			*((unsigned short *) virt_addr) = writeval;
		break;
		case 'w':
			*((unsigned int *) virt_addr) = writeval;
		break;
	}
	
	if (-1 == munmap (map_base, MAP_SIZE))
	{
		perror ("munmap error!");
		return -1;
	}
	close (fd);
	return 0;
}

//配置pwm下的节点文件的属性文件
static int pwm_config (const char *attr, const char *val, int node)
{
	//保存属性文件的路径
	char file_path[256];
	int fd, len;

	if (node == 0) {
		if ((strlen (pwm0_path) + strlen (attr)) > (sizeof(file_path)/sizeof(char))) {
			fprintf (stderr, "file path is too loong!\n");
			return -1;
		}
		sprintf (file_path, "%s/%s", pwm0_path, attr);
	} else if (node == 2) {
		if ((strlen (pwm2_path) + strlen (attr)) > (sizeof(file_path)/sizeof(char))) {
			fprintf (stderr, "file path is too loong!\n");
			return -1;
		}
		sprintf (file_path, "%s/%s", pwm2_path, attr);
	}
	if (0 > (fd = open (file_path, O_WRONLY))) {
		perror ("open attr file error!");
		return -1;
	}
	len = strlen (val);
	if (len != write (fd, val, len)) {
		perror ("write attr file error!");
		close (fd);
		return -1;
	}
	close (fd);
	return 0;
}

int main (int argc, char **argv)
{
	enum PWMX pwmx;

	//判断用户输入的命令是否合法
	if (argc != 2) {
		printf ("usage: ./pwm <num>\n");
		printf ("    num: 0, 2, 0-2\n");
		exit (-1);
	} else {
		if (strcmp (argv[1], "0") == 0) {
			pwmx = PWM0;
		}
		else if (strcmp (argv[1], "2") == 0) {
			pwmx = PWM2;
		}
		else if (strcmp (argv[1], "0-2") == 0) {
			pwmx = PWM0_PWM2;
		} else {
			printf ("usage: ./pwm <num>\n");
			printf ("    num: 0, 2, 0-2\n");
			exit (-1);
		}
	}
	
	//使能pwm控制器
	gpio_mode (PWM_ENABLE_REG, WRITE_TYPE_H, PWM_ENABLE_VAL);
	//配置gpio管脚的模式
	gpio_mode (GPIO_PAD_MODE_PWM0_CHANNEL0_REG, WRITE_TYPE_H, GPIO_PAD_MODE_PWM0_CHANNEL0_VAL);
	gpio_mode (GPIO_PAD_MODE_PWM2_CHANNEL0_REG, WRITE_TYPE_H, GPIO_PAD_MODE_PWM2_CHANNEL0_VAL);

	//将pwm0和pwm2的节点文件导出到用户空间 
	sprintf (pwm0_path, "/sys/class/pwm/pwmchip%s/pwm%s", PWM0_CONTROLLER, PWM0_CHANNEL0);
	sprintf (pwm2_path, "/sys/class/pwm/pwmchip%s/pwm%s", PWM2_CONTROLLER, PWM2_CHANNEL0);

	if (access(pwm0_path, F_OK)){
		int fd = 0;
		int len = 0;
		if (0 > (fd = open ("/sys/class/pwm/pwmchip0/export", O_WRONLY))){
			perror ("open");
			exit (-1);
		}
		len = strlen (PWM0_CHANNEL0);
		if (len != write (fd, PWM0_CHANNEL0, len))
		{
			perror ("write error");
			close (fd);
			exit (-1);
		}
	}
	if (access(pwm2_path, F_OK)){
		int fd = 0;
		int len = 0;
		if (0 > (fd = open ("/sys/class/pwm/pwmchip2/export", O_WRONLY))){
			perror ("open");
			exit (-1);
		}
		//注意还是用channel0!
		len = strlen (PWM0_CHANNEL0);
		if (len != write (fd, PWM0_CHANNEL0, len))
		{
			perror ("write error");
			close (fd);
			exit (-1);
		}
	}
	//配置pwm period,duty_cycle,enable
	//配置第0路pwm
	//a) 配置pwm的持续周期1ms
	if (pwm_config ("period", PWM_PERIOD, 0) < 0) {
		fprintf (stderr, "config pwm0 period attribute failed!");
		exit (-1);
	}
	//b) 配置占空比为0 
	if (pwm_config ("duty_cycle", PWM_DUTY1, 0) < 0) {
		fprintf (stderr, "config pwm0 duty attribute failed!");
		exit (-1);
	}
	//c) 使能pwm控制器
	if (pwm_config ("enable", "1", 0) < 0) {
		fprintf (stderr, "config pwm0 enable attribute failed!");
		exit (-1);
	}
	//配置第2路pwm
	//a) 配置pwm的持续周期1ms
	if (pwm_config ("period", PWM_PERIOD, 2) < 0) {
		fprintf (stderr, "config pwm2 period attribute failed!");
		exit (-1);
	}
	//b) 配置占空比为0 
	if (pwm_config ("duty_cycle", PWM_DUTY1, 2) < 0) {
		fprintf (stderr, "config pwm2 duty attribute failed!");
		exit (-1);
	}
	//c) 使能pwm控制器
	if (pwm_config ("enable", "1", 2) < 0) {
		fprintf (stderr, "config pwm2 enable attribute failed!");
		exit (-1);
	}
	
	unsigned long duty1 = atol(PWM_DUTY1);
	unsigned long duty2 = atol(PWM_DUTY2);
	unsigned long duty = duty1;
	int increase = 1;
	int step;
	char dutys[128];

	//定义调节占空比的步长
	step = (int)(duty2 - duty1) / 1000;
	printf ("duty1 = %ld, duty2 = %ld, step = %d\n", duty1, duty2, step);

	//自在呼吸 duty: 0 ~ 100, 100 ~ 0
	while (1) {
		if (increase == 1) {
			duty += step;
		} else if (increase == 0){
			duty -= step;
		}

		memset (dutys, 0, sizeof(dutys) / sizeof(char));
		sprintf (dutys, "%ld", duty);

		//根据用户的输入, 决定使用哪一路pwm
		//如果用户选择的是pwm0，则pwm2这一路不变， 配置占空比为最大
		if (pwmx == PWM2) {
			if (0 > pwm_config ("duty_cycle", PWM_DUTY1, 2)) {
				fprintf (stderr, "config pwm2 duty cycle error");
				exit (-1);
			}
			if (0 > pwm_config ("duty_cycle", dutys, 0)) {
				fprintf (stderr, "config pwm2 duty cycle error");
				exit (-1);
			}
		}
		//如果用户选择的是pwm2，则pwm0这一路不变， 配置占空比为最大
		if (pwmx == PWM2) {
			if (0 > pwm_config ("duty_cycle", PWM_DUTY1, 0)) {
				fprintf (stderr, "config pwm2 duty cycle error");
				exit (-1);
			}
			if (0 > pwm_config ("duty_cycle", dutys, 2)) {
				fprintf (stderr, "config pwm2 duty cycle error");
				exit (-1);
			}
		}
		//如果用户选择的是pwm2和pwm0, 这两路同时改变占空比
		if (pwmx == PWM0_PWM2) {
			if (0 > pwm_config ("duty_cycle", dutys, 0)) {
				fprintf (stderr, "config pwm2 duty cycle error");
				exit (-1);
			}
			if (0 > pwm_config ("duty_cycle", dutys, 2)) {
				fprintf (stderr, "config pwm2 duty cycle error");
				exit (-1);
			}
		}

		if (duty >= duty2) {
			increase = 0;
		} 
		if (duty <= duty1) {
			increase = 1;
		}
		usleep (2000);		
	}
	return 0;
}


